package br.mikhas.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.Set;

/**
 * Provide some methods to help working with numbers
 * 
 * @author Mikhail Domanoski
 */

public class Numbers {

	private Numbers() {
	}

	// Holds all default number classes
	private static final Set<Class<? extends Number>> NUMBERS;

	static {

		NUMBERS = new HashSet<Class<? extends Number>>(12);
		NUMBERS.add(short.class);
		NUMBERS.add(byte.class);
		NUMBERS.add(int.class);
		NUMBERS.add(long.class);
		NUMBERS.add(float.class);
		NUMBERS.add(double.class);
		NUMBERS.add(Short.class);
		NUMBERS.add(Integer.class);
		NUMBERS.add(Long.class);
		NUMBERS.add(Float.class);
		NUMBERS.add(Double.class);
		NUMBERS.add(Byte.class);

	}

	/**
	 * Checks if the object is an integer number
	 * 
	 * @param obj
	 *            The possible integer class
	 * @return <code>true</code> if it's a integer
	 */
	public static boolean isInteger(Object object) {
		return object == null ? false : object.getClass() == Integer.class
				|| object.getClass() == int.class;
	}

	/**
	 * Checks if the object is a float number
	 * 
	 * @param obj
	 *            The possible float class
	 * @return <code>true</code> if it's a float
	 */
	public static boolean isFloat(Object object) {
		return object == null ? false : object.getClass() == Float.class
				|| object.getClass() == float.class;
	}

	/**
	 * Checks if the object is a long number
	 * 
	 * @param obj
	 *            The possible long class
	 * @return <code>true</code> if it's a long
	 */
	public static boolean isLong(Object object) {
		return object == null ? false : object.getClass() == Long.class
				|| object.getClass() == long.class;
	}

	/**
	 * Checks if the object is a short number
	 * 
	 * @param obj
	 *            The possible short class
	 * @return <code>true</code> if it's a short
	 */
	public static boolean isShort(Object object) {
		return object == null ? false : object.getClass() == Short.class
				|| object.getClass() == short.class;
	}

	/**
	 * Checks if the object is a double number
	 * 
	 * @param obj
	 *            The possible double class
	 * @return <code>true</code> if it's a double
	 */
	public static boolean isDouble(Object object) {
		return object == null ? false : object.getClass() == Double.class
				|| object.getClass() == double.class;
	}

	/**
	 * Checks if the class is a class number
	 * 
	 * @param cls
	 *            The possible number class
	 * @return <code>true</code> if it's a number
	 */
	public static boolean isNumber(Class<?> cls) {
		return cls == null ? false : NUMBERS.contains(cls);
	}

	/**
	 * Checks if the object is a number
	 * 
	 * @param obj
	 *            The possible number object
	 * @return <code>true</code> if it's a number
	 */
	public static boolean isNumber(Object obj) {
		return obj == null ? false : NUMBERS.contains(obj.getClass());
	}

	/**
	 * Creates a numebr from a String according to the desired number class
	 * 
	 * @param clazz
	 *            The number class
	 * @param number
	 *            The string representation of the number
	 * @return and instance of the desired number
	 */
	@SuppressWarnings("unchecked")
	public static Number getNumber(Class<?> clazz, String number) {
		Assert.notNull(clazz);
		Assert.notEmpty(number);

		if (!isNumber(clazz))
			throw new IllegalArgumentException(
					"The providen class is not a number: " + clazz);

		Class<? extends Number> cls = null;
		if (TypeUtils.isWrapper(clazz)) {
			cls = (Class<? extends Number>) clazz;
		} else if (TypeUtils.isPrimitive(clazz)) {
			cls = (Class<? extends Number>) TypeUtils.getWrapper(clazz);
		}
		return instantiateNumber(cls, number);
	}

	/**
	 * Gets the lowest numbers in the array.
	 * 
	 * @param numbers
	 *            An array of numbers to look for the lowest one
	 * @return The lowest number
	 */
	public static <T extends Number> T min(T... numbers) {
		Assert.notEmpty(numbers);
		T min = numbers[0];

		for (int i = 1; i < numbers.length; i++) {
			if (compare(numbers[i], min) < 0)
				min = numbers[i];
		}

		return min;
	}

	/**
	 * Internally compare numbers
	 * 
	 * @param <T>
	 *            A number type
	 * @param a
	 *            The first number to compare
	 * @param b
	 *            The second number to compare
	 * @return The comparation of the numbers
	 */
	private static <T extends Number> int compare(T a, T b) {
		if (isInteger(a) && isInteger(b)) {
			return a.intValue() - b.intValue();
		} else if (isLong(a) && isLong(b)) {
			return (int) (a.longValue() - b.longValue());
		} else if (isDouble(a) && isDouble(b)) {
			return (int) (a.doubleValue() - b.doubleValue());
		} else if (isFloat(a) && isFloat(b)) {
			return (int) (a.floatValue() - b.floatValue());
		} else if (isShort(a) && isShort(b)) {
			return (int) (a.shortValue() - b.doubleValue());
		} else {
			return a.intValue() - b.intValue();
		}
	}

	/**
	 * Creates a new instance of the number
	 * 
	 * @param cls
	 *            The number class
	 * @param number
	 *            The string representation of the desired number
	 * @return The instance of the number
	 */
	@SuppressWarnings("unchecked")
	private static Number instantiateNumber(Class<? extends Number> cls,
			String number) {
		assert cls != null : "The class may not be null";

		if (number == null || number.isEmpty()) {
			return null;
		}

		try {
			Constructor<Number> numberConstructor = (Constructor<Number>) cls
					.getConstructor(String.class);
			Number resnum = numberConstructor.newInstance(number);
			return resnum;
		} catch (SecurityException e) {
			throw new RuntimeException(e);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException(e);
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		}
	}
}
